RISCV

您所在的位置:网站首页 c语言 原子操作有哪些 RISCV

RISCV

2024-06-18 18:53| 来源: 网络整理| 查看: 265

目录

1.  什么是原子操作

2. 原子操作的作用

3. 内存屏障

3.1 测试

 3.2 多进程加锁的理解

1.  什么是原子操作

        原子操作就是: 不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换(context switch)。

2. 原子操作的作用

        在多线程编程中,原子操作是必要的。因为在多线程环境中,如果有多个线程同时访问和修改共享资源,就可能会导致数据不一致的问题。而原子操作可以保证在任何情况下,对共享资源的访问都是原子的,即每个操作在执行过程中不会被其他线程打断,因此可以避免数据不一致的问题。

        多个独立的CPU运行时, 即使是可以在单个指令中完成的操作也可能会被干扰. 典型的例子就是decl指令(递减指令), 它细分为三个过程: "读->改->写", 涉及两次内存操作. 如果多个CPU运行的多个进程在同时对同一块内存执行这个指令, 那情况是无法预测的.。

借助原子操作可以实现互斥锁(mutex);我们对锁的访问就是原子的,我们希望锁只能一方获取,如果对一个变量赋值不是原子操作的,就会存在同一时刻,其中一个CPU读到变量为0,就以为当前资源不忙,可以操作,但是有可能这个值正在被另一个CPU修改,两个CPU都是0的时候进来的,都想赋值,只是一边还没完成,这就会出问题。原子操作无论你读的是什么,你打算写这个值,就会被正在操作的一方拒绝,即使这个值还没修改好,只要已经进行操作了就可以劝退其他CPU,这样就永远只能一个CPU获取资源控制权。借助互斥锁(mutex), 可以实现让更多的操作变成原子操作;互斥锁其实也是一种原子操作,我理解的是-原子操作是临界区极小的线程锁,线程锁是临界区较大的原子操作。 3. 内存屏障

        程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致,这就是内存乱序访问。内存乱序访问行为出现的理由是为了提升程序运行时的性能。内存乱序访问主要发生在两个阶段:

编译时,编译器优化导致内存乱序访问(指令重排)运行时,多 CPU 间交互引起内存乱序访问

        Memory Barrier 能够让 CPU 或编译器在内存访问上有序。一个 Memory Barrier 之前的内存访问操作必定先于其之后的完成。

3.1 测试 #include "stdio.h" int x = 0, y = 0, r = 0; void f() { x = r; y = 1; } int main() { f(); return 0; }

编译:

gcc -S test_memory_barrier.c

 结果:

    .file    "test_memory_barrier.c"     .text     .globl    x     .bss     .align 4     .type    x, @object     .size    x, 4 x:     .zero    4     .globl    y     .align 4     .type    y, @object     .size    y, 4 y:     .zero    4     .globl    r     .align 4     .type    r, @object     .size    r, 4 r:     .zero    4     .text     .globl    f     .type    f, @function f: .LFB0:     .cfi_startproc     pushq    %rbp     .cfi_def_cfa_offset 16     .cfi_offset 6, -16     movq    %rsp, %rbp     .cfi_def_cfa_register 6    movl    r(%rip), %eax     movl    %eax, x(%rip)     movl    $1, y(%rip)     nop     popq    %rbp     .cfi_def_cfa 7, 8     ret     .cfi_endproc .LFE0:     .size    f, .-f     .globl    main     .type    main, @function main: .LFB1:     .cfi_startproc     pushq    %rbp     .cfi_def_cfa_offset 16     .cfi_offset 6, -16     movq    %rsp, %rbp     .cfi_def_cfa_register 6     movl    $0, %eax     call    f     movl    $0, %eax     popq    %rbp     .cfi_def_cfa 7, 8     ret     .cfi_endproc .LFE1:     .size    main, .-main     .ident    "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"     .section    .note.GNU-stack,"",@progbits

 执行顺序是按序的。

改为2级优化后:

gcc -S -O2 test_memory_barrier.c

 .LFB23:    .cfi_startproc     movl    r(%rip), %eax     movl    $1, y(%rip)     movl    %eax, x(%rip)     ret

        顺序变了,这里先对Y赋值,后对X赋值,和写的代码顺序是不一致的。 

        单个CPU顺序优化,执行完结果一致,因为我们要的是运行完的结果,当多个CPU同时工作时,有CPU1可能在执行一半时,最终的值被CPU2访问到合法,CPU2要做某些工作,又会用到该值之前的变量数据,而之前的数据被CPU1优化的,结果都出来了,中间的数据还没算完,那就会出问题。 加个内存屏障:

#define mb() __asm__ __volatile__("" ::: "memory")

        阻止编译器重排,保证编译程序时在优化屏障之前的指令不会在优化屏障之后执行。 

#include "stdio.h" #define mb() __asm__ __volatile__("" ::: "memory") int x = 0, y = 0, r = 0; void f() { x = r; mb(); y = 1; } int main() { f(); return 0; }

结果:

.LFB23:     .cfi_startproc    movl    r(%rip), %eax     movl    %eax, x(%rip)     movl    $1, y(%rip)     ret

顺序恢复。

 3.2 多进程加锁的理解

        锁是原子操作的,那么这个锁值只会由一方更改,也就是只能一个进程获取锁;再者,通过内存屏蔽把临界区包进来,那么临界区的逻辑必须在锁之后且解锁前执行,不会因为优化乱序导致锁先解开了,共享区数据还没执行完,另一边又拿到锁改数据出现异常。我理解的原子操作加内存屏蔽一起保证了共享区的数据只能由一方操作。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3